package com.xiashitech.agent.diagnosis.server;

import com.xiashitech.agent.diagnosis.ClassDataSource;
import com.xiashitech.agent.diagnosis.command.*;
import com.xiashitech.agent.diagnosis.exception.CommandException;
import com.xiashitech.agent.diagnosis.exception.CommandInitializationException;
import com.xiashitech.agent.diagnosis.exception.CommandNotFoundException;
import com.xiashitech.agent.diagnosis.manager.ReflectManager;
import com.xiashitech.agent.diagnosis.manager.TimeFragmentManager;
import com.xiashitech.agent.diagnosis.util.DiagnosisHttpUtil;
import com.xiashitech.agent.diagnosis.util.DiagnosisJsonUtil;
import com.xiashitech.agent.diagnosis.util.GaStringUtils;
import com.xiashitech.agent.utils.JsonUtil;
import com.xiashitech.interfaces.agent.dto.diagnosis.CommandResp;
import org.apache.commons.lang3.ObjectUtils;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.xiashitech.agent.diagnosis.util.GaStringUtils.getCauseMessage;
import static java.lang.String.format;

/**
 * GaServer操作的附件
 */
class GaAttachment {

    private final int bufferSize;
    private final Session session;

    private LineDecodeState lineDecodeState;
    private ByteBuffer lineByteBuffer;


    GaAttachment(int bufferSize, Session session) {
        this.lineByteBuffer = ByteBuffer.allocate(bufferSize);
        this.bufferSize = bufferSize;
        this.lineDecodeState = LineDecodeState.READ_CHAR;
        this.session = session;
    }

    public LineDecodeState getLineDecodeState() {
        return lineDecodeState;
    }


    public void setLineDecodeState(LineDecodeState lineDecodeState) {
        this.lineDecodeState = lineDecodeState;
    }

    public void put(byte data) {
        if (lineByteBuffer.hasRemaining()) {
            lineByteBuffer.put(data);
        } else {
            final ByteBuffer newLineByteBuffer = ByteBuffer.allocate(lineByteBuffer.capacity() + bufferSize);
            lineByteBuffer.flip();
            newLineByteBuffer.put(lineByteBuffer);
            newLineByteBuffer.put(data);
            this.lineByteBuffer = newLineByteBuffer;
        }
    }

    public String clearAndGetLine(Charset charset) {
        lineByteBuffer.flip();
        final byte[] dataArray = new byte[lineByteBuffer.limit()];
        lineByteBuffer.get(dataArray);
        final String line = new String(dataArray, charset);
        lineByteBuffer.clear();
        return line;
    }

    public Session getSession() {
        return session;
    }

}

/**
 * 行解码
 */
enum LineDecodeState {

    // 读字符
    READ_CHAR,

    // 读换行
    READ_EOL
}

/**
 * Greys 服务端<br/>
 */
public class GaServer {

    protected static final Logger logger = Logger.getLogger("GaServer");

    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private static Boolean started = Boolean.FALSE;
    private static Boolean isDebugForDiagnosis = Boolean.FALSE;
    private final SessionManager sessionManager;
    private final CommandHandler commandHandler;
    private final int javaPid;
    private final Thread jvmShutdownHooker = new Thread("ga-shutdown-hooker") {

        @Override
        public void run() {
            GaServer.this._destroy();
        }
    };

    private final ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            final Thread t = new Thread(r, "ga-command-execute-daemon");
            t.setDaemon(true);
            return t;
        }
    });

    private GaServer(int javaPid, Instrumentation inst) {
        this.javaPid = javaPid;
        this.sessionManager = new DefaultSessionManager();
        this.commandHandler = new XSCommandHandler(this, inst);

        initForManager(inst);

        Runtime.getRuntime().addShutdownHook(jvmShutdownHooker);

    }

    /*
     * 初始化各种manager
     */
    private void initForManager(final Instrumentation inst) {
        TimeFragmentManager.Factory.getInstance();
        ReflectManager.Factory.initInstance(new ClassDataSource() {
            @Override
            public Collection<Class<?>> allLoadedClasses() {
                final Class<?>[] classArray = inst.getAllLoadedClasses();
                return null == classArray
                        ? new ArrayList<Class<?>>()
                        : Arrays.asList(classArray);
            }
        });
    }

    public static void init(final Instrumentation instrumentation, String agentService, String systemId, String microServiceId) {
        try {
            String name = ManagementFactory.getRuntimeMXBean().getName();
            String pid = name.split("@")[0];
            getInstance(Integer.valueOf(pid),instrumentation);
            DiagnosisHttpUtil.setAgentService(agentService);
            DiagnosisHttpUtil.setSystemId(systemId);
            DiagnosisHttpUtil.setMicroServiceId(microServiceId);
        } catch (Throwable e) {
        }
    }

    public static void determineStartDiagnosis() {
        if(gaServer == null || started) {
            return;
        }
        gaServer.start();
    }

    public void start() {
        final Thread gaServerDaemon = new Thread("ga-server-daemon") {
            @Override
            public void run() {
                while (isDebugForDiagnosis && !executorService.isShutdown()) {
                    try {
                        String line = null;
                        Integer sessionId = null;
                        String diagnosisCommand = DiagnosisHttpUtil.getDiagnosisCommand();
                        if(diagnosisCommand != null && !diagnosisCommand.isEmpty() && JsonUtil.isJson(diagnosisCommand)) {
                            CommandResp commandResp = DiagnosisJsonUtil.convertStringToObject(diagnosisCommand, CommandResp.class);
                            if(DiagnosisHttpUtil.getSystemId().equals(commandResp.getSystemId()) && DiagnosisHttpUtil.getMicroServiceId().equals(commandResp.getMicroServiceId())) {
                                line = commandResp.getLine();
                                sessionId = commandResp.getSessionId();
                            }
                            if(GaStringUtils.isNotEmpty(line)) {
                                final Session session;
                                if(ObjectUtils.isEmpty(sessionId)) {
                                    session = sessionManager.newSession(javaPid, DEFAULT_CHARSET);
                                } else {
                                    session = sessionManager.getSession(sessionId);
                                }
                                String finalLine = line;
                                Integer finalSessionId = sessionId;
                                executorService.execute(new Runnable() {
                                    @Override
                                    public void run() {
                                        try {
                                            final Command command = Commands.getInstance().newCommand(finalLine);
                                            // 会话只有未锁定的时候才能响应命令
                                            if (session != null && (session.tryLock() || command instanceof QuitCommand || command instanceof ShutdownCommand || command instanceof ResetCommand)) {
                                                try {
                                                    //命令执行
                                                    commandHandler.executeCommand(finalLine, command, session);
                                                } catch (IOException e) {
                                                    logger.log(Level.INFO,"network communicate failed, session["+session.getSessionId()+"] will be close.");
                                                    session.destroy();
                                                } finally {
                                                    session.unLock();
                                                }
                                            } else {
                                                logger.log(Level.INFO,"the session was locked or not found, ignore this command.");
                                                if(session != null) {
                                                    DiagnosisHttpUtil.sendResult(session.getSessionId(),finalLine,"the session was locked or not found, ignore this command.");
                                                } else {
                                                    DiagnosisHttpUtil.sendResult(finalSessionId,finalLine,"the session was locked or not found, ignore this command.");
                                                }
                                            }
                                        } catch (CommandException t) {//命令准备错误(参数校验等)
                                            final String message;
                                            if (t instanceof CommandNotFoundException) {
                                                message = format("Command \"%s\" not found.", t.getCommand());
                                            } else if (t instanceof CommandInitializationException) {
                                                message = format("Command \"%s\" failed to initiate.", t.getCommand());
                                            } else {
                                                message = format("Command \"%s\" preprocessor failed : %s.", t.getCommand(), getCauseMessage(t));
                                            }
                                            logger.log(Level.INFO,message+":"+t.getMessage());
                                            //输出错误信息
                                            DiagnosisHttpUtil.sendResult(session.getSessionId(),finalLine,message+":"+t.getMessage());
                                        }
                                    }
                                });
                                //命令执行完发送通知
                                DiagnosisHttpUtil.sendResult(session.getSessionId(),finalLine,"The command has been processed!");
                            }
                        }
                        sleep(2000);
                    } catch (Throwable e) {
                    }
                    if(!isDebugForDiagnosis) {
                        setStarted(Boolean.FALSE);
                    }
                }
            }
        };
        gaServerDaemon.setDaemon(true);
        gaServerDaemon.start();
        setStarted(Boolean.TRUE);
    }


    private void _destroy() {

        if (!sessionManager.isDestroy()) {
            sessionManager.destroy();
        }

        executorService.shutdown();

        setStarted(Boolean.FALSE);

        gaServer = null;

        logger.info("ga-server destroy completed.");
    }

    public void destroy() {
        Runtime.getRuntime().removeShutdownHook(jvmShutdownHooker);
        _destroy();
    }

    private static volatile GaServer gaServer;

    /**
     * 单例
     *
     * @param instrumentation JVM增强
     * @return GaServer单例
     */
    public static GaServer getInstance(final int javaPid, final Instrumentation instrumentation) {
        if (null == gaServer) {
            synchronized (GaServer.class) {
                if (null == gaServer) {
                    gaServer = new GaServer(javaPid, instrumentation);
                }
            }
        }
        return gaServer;
    }

    public static void setStarted(Boolean started) {
        GaServer.started = started;
    }

    public static void setIsDebugForDiagnosis(Boolean isDebugForDiagnosis) {
        GaServer.isDebugForDiagnosis = isDebugForDiagnosis;
    }

    public static Boolean getIsDebugForDiagnosis() {
        return isDebugForDiagnosis;
    }
}
